package com.superbytecode.cloud.safety.ratelimit.limiter;

import com.superbytecode.cloud.safety.ratelimit.RateLimiter;
import com.superbytecode.cloud.safety.ratelimit.exception.RateLimitException;
import com.superbytecode.cloud.safety.ratelimit.model.RateLimitReq;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;

/**
 * All rights Reserved, Designed By www.super-master.com
 *
 * @ProjectName: beacon-cloud
 * @Package: com.superbytecode.cloud.safety.ratelimit.limiter
 * @ClassName: CustomRateLimiter
 * @Description: [自定义参数限流器]
 * <p> </p>
 * @Author: [Lance Ting]
 * @Date: 2024/11/13 15:12
 * @Version: V1.0
 * @Copyright: 2024 www.super-master.com Inc. All rights reserved.
 * TODO: 注意,本文件Lance Ting所作,如果转载或使用请标明具体出处!
 **/
@Slf4j
public class CustomRateLimiter extends RateLimiter {
    private static final SpelExpressionParser SPEL_PARSER;
    private static final LocalVariableTableParameterNameDiscoverer PARAMETER_NAME_DISCOVERER;
    private static final EvaluationContext evaluationContext;

    static {
        SPEL_PARSER = new SpelExpressionParser();
        PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
        evaluationContext = new StandardEvaluationContext();
    }

    /**
     * 获取key
     *
     * @param rateLimitReq 请求参数
     * @return key
     */
    @Override
    public String obtainKey(RateLimitReq rateLimitReq) {
        ProceedingJoinPoint joinPoint = rateLimitReq.getJoinPoint();
        String classAndMethodName = obtainMethodName(joinPoint);
        try {
            // 解析springEl
            refreshEvaluationContext(joinPoint);
            String spEl = rateLimitReq.getSpEl();
            String value = SPEL_PARSER.parseExpression(spEl).getValue(evaluationContext, String.class);
            log.info("限流表达式{}, 解析结果:{}", spEl, value);
            if (StringUtils.isBlank(value)) {
                log.warn("{}上的注解@RateLimit的apEl获取空属性，spEl参数将失效", classAndMethodName);
            }
            return value;
        } catch (RuntimeException e) {
            throw new EvaluationException(classAndMethodName + "注解@RateLimit的spEl属性指定有误，无法解析spEl表达式：", e);
        }
    }

    private void refreshEvaluationContext(ProceedingJoinPoint joinPoint) {
        Method method = obtainMethod(joinPoint);
        String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
        if (null == parameterNames || parameterNames.length == 0) {
            // 方法没有入参
            log.error("{}方法没有入参", obtainMethodName(joinPoint));
            throw new RateLimitException("限流方法没有入参");
        }
        // 获取方法参数列表
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            // 替换表达式里的变量值为实际值，p0,p1或者原参数名
            evaluationContext.setVariable(parameterNames[i], args[i]);
            evaluationContext.setVariable("p" + i, args[i]);
        }
    }

    private String obtainMethodName(ProceedingJoinPoint joinPoint) {
        return "@" + obtainClass(joinPoint).getName() + "." + obtainMethod(joinPoint).getName();
    }

    private Method obtainMethod(ProceedingJoinPoint joinPoint) {
        return ((MethodSignature) joinPoint.getSignature()).getMethod();
    }

    private Class<?> obtainClass(ProceedingJoinPoint joinPoint) {
        return joinPoint.getTarget().getClass();
    }
}